package kino.server;

import io.netty.channel.Channel;
import kino.aof.AofService;
import kino.client.KinoClient;
import kino.command.CommandInterpreter;
import kino.common.KinoContext;
import kino.database.KinoDB;
import kino.timeevent.KinoTimeEventRegistry;
import kino.timeevent.TimeEvent;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.nio.channels.SocketChannel;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

@Component
public class KinoServer implements Runnable, InitializingBean {

    /**
     * 运行id每次启动的时候自动生成用于主从同步时的身份标识
     */
    private String runId;
    /**
     * 数据库列表
     */
    private List<KinoDB> dbList;
    /**
     * 默认初始化数据库数量
     */
    @Value("${kino.database.num:1}")
    private Integer dbNum;
    /**
     * 客户端列表
     */
    private List<KinoClient> clientList;
    /**
     * 客户端句柄映射
     */
    private Map<Channel, KinoClient> clientNettyChannelMap;

    @Autowired
    private CommandInterpreter commandInterpreter;

    /**
     * 命令&时间事件 执行执行器 以单线程 + 临界资源保证 全局串行
     */
    private ExecutorService taskExecutor;
    /**
     * aof相关
     */
    @Autowired
    private AofService aofService;

    @Autowired
    private KinoContext kinoContext;

    /**
     * 使事件和命令同步执行 的 同步器
     */
    private KinoSync kinoSync;

    /**
     * 时间事件注册器
     */
    private KinoTimeEventRegistry kinoTimeEventRegistry;

    /**
     * 同步器 保障 命令执行、时间事件都处于串行执行,前期版本用Semaphore实现后续根据场景调整
     */
    class KinoSync extends Semaphore {
        public KinoSync() {
            super(1, true);
        }
    }
    public KinoServer() {
    }

    /**
     * kino服务端初始化
     */
    private void initServer() {
        // 创建运行id
        this.runId = UUID.randomUUID().toString();
        // 初始化数据库列表 并 设置默认数据库
        this.dbList = new ArrayList<>();
        for (int i = 0 ; i < dbNum ; i++ ) {
            dbList.add(new KinoDB(i));
        }
        // 初始化客户端列表
        this.clientList = new ArrayList<>();
        // 客户端通道维护map netty版
        this.clientNettyChannelMap = new HashMap<>();
        // 创建线命令执行线程池
        this.taskExecutor = Executors.newSingleThreadExecutor();
        // 同步器初始化
        this.kinoSync = new KinoSync();
        // 时间事件注册初始化
        this.kinoTimeEventRegistry = new KinoTimeEventRegistry();
    }

    @Override
    public void run() {
        // 初始化服务端
        this.initServer();
        // 加载aof文件
        try {
            // 等待命令执行完毕
            kinoContext.commandLoading.await();
        } catch (InterruptedException e) {
            // 空
        } finally {
            this.aofService.loadDataFromDisk(this);
        }

        while(true) {
            try {
                // 前置处理
                this.beforeProcessor();
                // 获取下个时间事件执行事件
                long nextTimeEventTime = this.getNextTimeEventTime();
                // 计算本次循环文件事件允许阻塞多久
                long timeout = nextTimeEventTime - System.currentTimeMillis();
                timeout = timeout < 0 ? 0 : timeout;
                // 文件事件|当前调整为仅休眠
                this.fileEvent(timeout);
                // 时间事件
                this.timeEvent();
            } catch (Exception e) {
            }
        }
    }

    public List<KinoClient> getClientList() {
        return clientList;
    }

    public void setClientList(List<KinoClient> clientList) {
        this.clientList = clientList;
    }

    public KinoDB defaultDb() {
        return dbList.get(0);
    }

    public void addClient(KinoClient client) {
        this.clientList.add(client);
        // netty具柄
        if (client.getChannel() != null) {
            this.clientNettyChannelMap.put(client.getChannel(), client);
        }
    }

    public void clientLoginOut(KinoClient client){
        this.clientList.remove(client);
        // netty具柄
        for (Channel socketChannel : clientNettyChannelMap.keySet()) {
            if (this.clientNettyChannelMap.get(socketChannel) == client) {
                this.clientNettyChannelMap.remove(socketChannel);
                break;
            }
        }
    }

    public KinoClient getKinoClientByChannel(Channel channel) {
        return this.clientNettyChannelMap.get(channel);
    }

    public CommandInterpreter getCommandInterpreter() {
        return commandInterpreter;
    }

    public void setCommandInterpreter(CommandInterpreter commandInterpreter) {
        this.commandInterpreter = commandInterpreter;
    }
    public Integer getDbNum() {
        return dbNum;
    }

    public void setDbNum(Integer dbNum) {
        this.dbNum = dbNum;
    }

    public KinoDB getDbByNo(Integer dbNo) {
        return dbList.get(dbNo);
    }

    public String getRunId() {
        return runId;
    }

    public void setRunId(String runId) {
        this.runId = runId;
    }
    private void beforeProcessor() throws IOException {
        // NIO模式下采用每次循环时将命令返回写出，netty模式采用命令执行完成直接写出 当前情况下暂留空
    }
    private void fileEvent(Long timeout) throws InterruptedException {
        // 使用netty客户端的情况下，这个地方实际上已经没有用了 当前的作用相当于sleep
        // kinoNioServer.listen(timeout);
        Thread.sleep(timeout);
    }

    private void timeEvent() throws InterruptedException {
        // TODO 执行 TimeEvent抽象类实现的多个达到可执行时间的周期事件
        List<TimeEvent> readyTimeEventList = kinoTimeEventRegistry.getReadyTimeEvent();
        // 初始化同步器
        kinoContext.initTimeEventLoading(readyTimeEventList.size());
        for (TimeEvent timeEvent : readyTimeEventList) {
            // 设置同步器
            timeEvent.setTimeEventLoading(kinoContext.getTimeEventLoading());
            // 执行时间事件
            this.execute(timeEvent);
        }
        // 等待事件执行完毕
        kinoContext.getTimeEventLoading().await();
        // 重新将事件加入等待列表
        kinoTimeEventRegistry.reSetTimeEvent(readyTimeEventList);

    }

    private long getNextTimeEventTime() {
        // 获取最近的一共时间事件执行时间
        return kinoTimeEventRegistry.getNextTimeEventTime();
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        new Thread(this).start();
    }

    /**
     * 该方法作为server全局命令执行的限制临界区，包括时间事件和文件时间都是单线程执行
     * @param runnable
     */
    public void execute(Runnable runnable) {
        try {
            // 命令执行临界资源（时间事件和命令执行只能同时进行一个事情）
            kinoSync.acquire();
            // 命令执行
            taskExecutor.execute(runnable);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 命令执行临界资源
            kinoSync.release();
        }
    }

}
